package ru.spbu.math.seabattle.entity;

import java.util.Stack;

import ru.spbu.math.seabattle.client.IClient;
import ru.spbu.math.seabattle.entity.FieldModel.Cell;

public class Game {

	private IClient firstPlayer;
	private IClient secondPlayer;
	private PlayerID firstID;
	private PlayerID secondID;

	private FieldModel firstField;
	private FieldModel secondField;
	
	private String firstPlayerName;
	private String secondPlayerName;
	
	private Integer firstActiveCells = 20;
	private Integer secondActiveCells = 20;

	public void init(IClient first, PlayerID firstID, IClient second,
			PlayerID secondID, String firstPlayerName, String secondPlayerName) {

		if (firstPlayer != null || secondPlayer != null) {
			throw new UnsupportedOperationException(
					"The players are already initialized");
		}
		if (first == null || second == null) {
			throw new NullPointerException("Some of the players are null");
		}

		firstPlayer = first;
		secondPlayer = second;

		this.firstID = firstID;
		this.secondID = secondID;
		
		this.firstPlayerName = firstPlayerName;
		this.secondPlayerName = secondPlayerName;
	}

	public void setField(PlayerID id, FieldModel field) {

		if (id.equals(firstID)) {
			if (firstField != null) {
				throw new IllegalArgumentException(
						"The first player field is already initialized.");
			}
			firstField = new FieldModel(field);
			firstField.print();
		} else {
			if (secondField != null) {
				throw new IllegalArgumentException(
						"The second player field is already initialized.");
			}
			secondField = new FieldModel(field);
			secondField.print();
		}

		if (firstField != null && secondField != null) {
			firstPlayer.onYourTurnStarted();
		}
	}
	
	private boolean checkIfShipIsDestroyed(PlayerID id, Action action) {
		
		System.out.println("SHIP IS DESTROYED!!!");
		int fieldSize = firstField.FIELD_SIZE;
		
		//This field belongs to that, who was shot.
		FieldModel field;
		
		if (id.equals(firstID)) {
			field = secondField;
		} else {
			field = firstField;
		}
		
		int i = action.i + 1;
		int j = action.j;
		
		while (i < fieldSize) {
			if (field.isShipCell(i, j)) {
				return false;
			}
			if (field.isEmptyCell(i, j) || field.isShotCell(i, j)) {
				break;
			}
			i++;
		}
		
		i = action.i - 1;
		
		while (i >= 0) {
			if (field.isShipCell(i, j)) {
				return false;
			}
			if (field.isEmptyCell(i, j) || field.isShotCell(i, j)) {
				break;
			}
			i--;
		}
		
		i = action.i;
		j = action.j + 1;
		
		while (j < fieldSize) {
			if (field.isShipCell(i, j)) {
				return false;
			}
			if (field.isEmptyCell(i, j) || field.isShotCell(i, j)) {
				break;
			}
			j++;
		}
		
		j = action.j - 1;
		
		while (j >= 0) {
			if (field.isShipCell(i, j)) {
				return false;
			}
			if (field.isEmptyCell(i, j) || field.isShotCell(i, j)) {
				break;
			}
			j--;
		}
		
		return true;
	}
	
	private class FieldCell {
		
		public final int i;
		public final int j;
		
		public FieldCell(int i, int j) {
			this.i = i;
			this.j = j;
		}
		
		public void surroundByShotCells(boolean first) {
			
			FieldModel field;
			IClient player;
			
			if (first) {
				field = firstField;
				player = secondPlayer;
			} else {
				field = secondField;
				player = firstPlayer;
			}
			int size = field.FIELD_SIZE;
			
			for (int di = -1; di <= 1; di++) {
				for (int dj = -1; dj <= 1; dj++) {
					
					if (i + di < 0 || i + di >= size || j + dj < 0 || j + dj >= size) {
						continue;
					}
					
					if (field.isEmptyCell(i + di, j + dj)) {
						if (di == 0 && dj == 0) {
							continue;
						}
						field.setShotCell(i + di, j + dj);
						player.onYourTurnEnded(new TurnResult(false, new Action(i + di, j + dj)));
						player.onYourTurnStarted();
					}
				}
			}
		}
	}
	
	private FieldCell[] getShip(boolean isFirst, Action action) {
		
		Stack<FieldCell> result = new Stack<FieldCell>();
		FieldModel field;
		
		if (isFirst) {
			field = secondField;
		} else {
			field = firstField;
		}
		
		int size = field.FIELD_SIZE;
		
		int i = action.i + 1;
		int j = action.j;
		
		result.add(new FieldCell(action.i, action.j));
		
		while (i < size && (field.isShipCell(i, j) || field.isDemagedShipCell(i, j))) {
			result.add(new FieldCell(i, j));
			i++;
		}
		
		i = action.i - 1;
		
		while (i >= 0 && (field.isShipCell(i, j) || field.isDemagedShipCell(i, j))) {
			result.add(new FieldCell(i, j));
			i--;
		}
		
		i = action.i;
		j = action.j + 1;
		
		while (j < size && (field.isShipCell(i, j) || field.isDemagedShipCell(i, j))) {
			result.add(new FieldCell(i, j));
			j++;
		}
		
		j = action.j - 1;
		
		while (j >= 0 && (field.isShipCell(i, j) || field.isDemagedShipCell(i, j))) {
			result.add(new FieldCell(i, j));
			j--;
		}
		
		FieldCell[] res = new FieldCell[result.size()];
		int s = 0;
		while (!result.isEmpty()) {
			res[s++] = result.pop();
		}
		
		return res;
	}

	/**
	 * 
	 * @param id The id of the player, who made turn.
	 * @param action
	 * @return Returns true if the game is over after this step. Otherwise
	 *         returns false.
	 */
	public boolean onTurnMade(PlayerID id, Action action) {

		if (id.equals(firstID)) {
			
			System.out.println("GAME: ON_TURN_MADE by : " + firstPlayerName + ": " + action.i + " " + action.j);
			
			TurnResult result = new TurnResult(secondField.isShipCell(action), action);
			firstPlayer.onYourTurnEnded(result);
			secondPlayer.onEnemyTurnEnded(result);
			
			if (result.success) {

				boolean destroyed = checkIfShipIsDestroyed(id, action);
				
				if (destroyed) {
					FieldCell[] ship = getShip(true, action);
					for (FieldCell cell : ship) {
						cell.surroundByShotCells(false);
					}
				}
				
				secondField.setCell(action.i, action.j, Cell.DAMAGED_SHIP);
				secondActiveCells--;
				
				if (secondActiveCells == 0) {
					
					GameResult gameResult = new GameResult(firstID ,secondID, firstPlayerName, secondPlayerName);
					secondPlayer.onGameEnded(gameResult);
					firstPlayer.onGameEnded(gameResult);
					return true;
				} else {
					
					secondPlayer.onEnemyTurnStarted();
					firstPlayer.onYourTurnStarted();
					return false;
				}
			} else {
				
				secondField.setShotCell(action.i, action.j);
				firstPlayer.onEnemyTurnStarted();
				secondPlayer.onYourTurnStarted();
				return false;
			}
		} else if (id.equals(secondID)) {
			
			System.out.println("GAME: ON_TURN_MADE by : " + secondPlayerName + ": " + action.i + " " + action.j);
			
			TurnResult result = new TurnResult(firstField.isShipCell(action), action);
			secondPlayer.onYourTurnEnded(result);
			firstPlayer.onEnemyTurnEnded(result);
			
			if (result.success) {
				
				boolean destroyed = checkIfShipIsDestroyed(id, action);
				
				if (destroyed) {
					FieldCell[] ship = getShip(false, action);
					for (FieldCell cell : ship) {
						cell.surroundByShotCells(true);
					}
				}
				
				firstField.setCell(action.i, action.j, Cell.DAMAGED_SHIP);
				firstActiveCells--;
				
				if (firstActiveCells == 0) {
				
					GameResult gameResult = new GameResult(secondID, firstID, secondPlayerName, firstPlayerName);
					firstPlayer.onGameEnded(gameResult);
					secondPlayer.onGameEnded(gameResult);
					return true;
				} else {
					
					firstPlayer.onEnemyTurnStarted();
					secondPlayer.onYourTurnStarted();
					return false;
				}
			} else {
				
				firstField.setShotCell(action.i, action.j);
				secondPlayer.onEnemyTurnStarted();
				firstPlayer.onYourTurnStarted();
				return false;
			}
		} else {
			throw new IllegalStateException("There is no player with such id in this game!  We have " + firstID + " and " + secondID + ", but you entered " + id);
		}
		
	}
	
	public PlayerID getAnotherPlayer(PlayerID id) {
		if (id.equals(firstID)) {
			return secondID;
		} else if (id.equals(secondID)) {
			return firstID;
		} else {
			return null;
		}
	}
}
